﻿/**
<code>
  <author name="Zealic" email="zealic(at)gmail.com" type="Creator"/>
</code>
**/
using System;
using System.Collections;


namespace Zealib.Collections
{
    /// <summary>
    /// WeakReference value hashtable.
    /// </summary>
    public class WeakHashtable : Hashtable
    {
        private class WeakHashtableEnumerator : IDictionaryEnumerator
        {
            private IDictionaryEnumerator m_RealEnumerator;

            public WeakHashtableEnumerator(IDictionaryEnumerator enumerator)
            {
                m_RealEnumerator = enumerator;
            }

            public DictionaryEntry Entry
            {
	            get { return m_RealEnumerator.Entry; }
            }

            public object Key
            {
	            get { return m_RealEnumerator.Key; }
            }

            public object Value
            {
	            get
	            {
	                WeakReference valueProxy = m_RealEnumerator.Value as WeakReference;
	                if(valueProxy == null) return null;
                    return valueProxy.Target;
	            }
            }

            public object Current
            {
	            get { return m_RealEnumerator.Current; }
            }

            public bool MoveNext()
            {
                return m_RealEnumerator.MoveNext();
            }

            public void Reset()
            {
 	            m_RealEnumerator.Reset();
            }

        }

        private DateTime m_LastAccess = DateTime.Now;
        private int m_ScavengeLevel = 0;

        #region Constructor
		public WeakHashtable()
            : base(0, 1F)
        {
        }

        public WeakHashtable(IDictionary d) 
            : this(d, 1F)
        {
        }

        public WeakHashtable(IDictionary d, IEqualityComparer equalityComparer) 
            : this(d, 1f, equalityComparer)
        {
        }

        public WeakHashtable(IDictionary d, float loadFactor) 
            : this(d, loadFactor, null)
        {
        }

         public WeakHashtable(IDictionary d, float loadFactor, IEqualityComparer equalityComparer)
            : base((d != null) ? d.Count : 0, loadFactor, equalityComparer)
         {
             if(d == null) throw new ArgumentNullException("d");
             IDictionaryEnumerator enumerator = d.GetEnumerator();
             while (enumerator.MoveNext())
             {
                 Add(enumerator.Key, enumerator.Value);
             }
         }

        public WeakHashtable(int capacity) 
            : base(capacity, 1F)
        {
        }

        public WeakHashtable(IEqualityComparer equalityComparer) 
            : base(0, 1F, equalityComparer)
        {
        }
        
        public WeakHashtable(int capacity, float loadFactor)
            : base(capacity,loadFactor)
        {
        }

        public WeakHashtable(int capacity, IEqualityComparer equalityComparer) 
            : base(capacity, 1f, equalityComparer)
        {
        }

        public WeakHashtable(int capacity, float loadFactor, IEqualityComparer equalityComparer)
            : base(capacity, loadFactor, equalityComparer)
        {
        }
        
        protected  virtual WeakReference CreateWeakReference(object value)
        {
            if(value == null) throw new ArgumentNullException("value");
            return new WeakReference(value);
        }

        #endregion


        protected virtual bool RequireScavenge(int scavengeLevel)
        {
            DateTime now = DateTime.Now;
            TimeSpan span = now.Subtract(m_LastAccess);
            if (span > new TimeSpan(TimeSpan.TicksPerMinute))
            {
                m_ScavengeLevel += span.Minutes * Count;
            }
            m_LastAccess = now;
            m_ScavengeLevel += scavengeLevel;

            if (m_ScavengeLevel > Count * 10)
            {
                m_ScavengeLevel = 0;
                Scavenge();
            }
            return true;
        }

        public void Scavenge()
        {
            IList rubbish = new ArrayList();
            IEnumerator enumerator = base.GetEnumerator();

            while (enumerator.MoveNext())
            {
                DictionaryEntry entry = (DictionaryEntry)enumerator.Current;
                WeakReference value = entry.Value as WeakReference;
                if (value == null)
                    continue;
                if (!value.IsAlive)
                    rubbish.Add(entry.Key);
            }

            foreach (object key in rubbish)
            {
                Remove(key);
            }
        }

        public override void Add(object key, object value)
        {
            RequireScavenge(1);
            if(value == null)
                base.Add(key, CreateWeakReference(value));
            else
                base.Add(key, null);
        }

        public override void CopyTo(Array array, int arrayIndex)
        {
            RequireScavenge(Count / 2);
            base.CopyTo(array, arrayIndex);
            for(int i = arrayIndex; i < array.Length; i++)
            {
                DictionaryEntry entry = (DictionaryEntry)array.GetValue(i);
                if(entry.Value == null) continue;
                WeakReference wrapper = entry.Value as WeakReference;
                if(wrapper == null)
                    continue;
                entry.Value = wrapper.Target;
            }

        }

        public override bool ContainsValue(object value)
        {
            RequireScavenge(1);
            IEnumerator enumerator = base.GetEnumerator();

            while(enumerator.MoveNext())
            {
                DictionaryEntry entry = (DictionaryEntry)enumerator.Current;
                WeakReference entryValue = entry.Value as WeakReference;
                if(entryValue == null)
                    continue;
                if(ReferenceEquals(entryValue.Target, value))
                    return true;
            }
            return false;
        }

        public override object Clone()
        {
            RequireScavenge(Count);
            WeakHashtable hashtable = new WeakHashtable(Count);
            foreach (DictionaryEntry entry in this)
                hashtable.Add(entry.Key, entry.Value);
            return hashtable;
        }

        public override IDictionaryEnumerator GetEnumerator()
        {
            RequireScavenge(Count);
            return new WeakHashtableEnumerator(base.GetEnumerator());
        }

        public override object this[object key]
        {
            get
            {
                RequireScavenge(1);
                WeakReference wrapper = base[key] as WeakReference;
                if (wrapper == null) return null;
                if (!wrapper.IsAlive)
                {
                    base.Remove(wrapper);
                    return null;
                }
                return wrapper.Target;
            }
            set
            {
                RequireScavenge(1);
                if (value == null)
                {
                    base[key] = null;
                }
                else
                {
                    WeakReference wrapper = CreateWeakReference(value);
                    base[key] = wrapper;
                }
            }
        }

    }
}